import tkinter as tk
from tkinter import ttk, messagebox, simpledialog, Toplevel, Listbox, MULTIPLE, END, Checkbutton, IntVar, LabelFrame, \
    font as tkfont
import random
import json
import time  # For incremental display and mock delay
import requests  # For actual API calls
import threading  # For running API calls in a separate thread
import queue  # For communication between threads

# --- Constants (保持不变) ---
PLAYER_NAMES = ["老张", "阿兰", "阿阳", "肯子", "陈飞", "东哥", "小黄", "为哥"]
# MODIFIED: 更新8人局角色配置
ROLES_FULL_SET = ["梅林", "派西维尔", "骑士", "骑士", "骑士", "莫甘娜", "刺客", "爪牙"]  # 8 players, 5 Blue, 3 Red
ROLE_FACTIONS = {
    "梅林": "Blue", "派西维尔": "Blue", "骑士": "Blue",
    "莫甘娜": "Red", "刺客": "Red", "爪牙": "Red"}

ROUND_TEAM_SIZES = {1: 3, 2: 4, 3: 4, 4: 5, 5: 5}

# --- 文心API凭证 (保持不变) ---
WENXIN_DISCUSSION_APP_ID = "--"
WENXIN_DISCUSSION_SECRET_KEY = "--"
WENXIN_DISCUSSION_USER_OPEN_ID = "user_discussion_001"
WENXIN_ANALYSIS_APP_ID = "--"
WENXIN_ANALYSIS_SECRET_KEY = "--"
WENXIN_ANALYSIS_USER_OPEN_ID = "user_analysis_001"


# --- 文心API调用函数 (call_wenxin_api_actual - 保持不变) ---
def call_wenxin_api_actual(payload_data, service_type):
    app_id, secret_key, user_open_id = None, None, None
    if service_type == "discussion":
        app_id, secret_key, user_open_id = WENXIN_DISCUSSION_APP_ID, WENXIN_DISCUSSION_SECRET_KEY, WENXIN_DISCUSSION_USER_OPEN_ID
    elif service_type == "analysis":
        app_id, secret_key, user_open_id = WENXIN_ANALYSIS_APP_ID, WENXIN_ANALYSIS_SECRET_KEY, WENXIN_ANALYSIS_USER_OPEN_ID
    else:
        return None, "无效的服务类型"

    # 简单凭证检查
    if any(val is None or val.startswith("YOUR_") or not val for val in [app_id, secret_key]):
        return None, f"{service_type.capitalize()} API的凭证未正确配置"

    print(f"\n--- [DEBUG] START API CALL ({service_type}) ---")
    try:
        serialized_value_for_showtext = json.dumps(payload_data, ensure_ascii=False, indent=2)  # indent for readability
        print(f"--- [DEBUG] PAYLOAD SENDING TO API ({service_type}) ---")
        print(serialized_value_for_showtext)
        print(f"--- [DEBUG] END OF PAYLOAD ({service_type}) ---")
    except TypeError as e:
        print(f"--- [DEBUG] ERROR DURING PAYLOAD SERIALIZATION ({service_type}) ---")
        print(f"Error: {e}")
        print(f"--- [DEBUG] END API CALL ({service_type}) ---")
        return None, f"序列化输入数据错误: {e}"

    url = f"https://agentapi.baidu.com/assistant/getAnswer?appId={app_id}&secretKey={secret_key}"
    headers = {"Content-Type": "application/json"}
    api_request_payload = {
        "message": {"content": {"type": "text", "value": {"showText": serialized_value_for_showtext}}},
        "source": app_id, "from": "openapi", "openId": user_open_id
    }

    response_obj = None

    try:
        response_obj = requests.post(url, headers=headers,
                                     data=json.dumps(api_request_payload, ensure_ascii=False).encode('utf-8'),
                                     timeout=180)

        print(f"--- [DEBUG] RAW RESPONSE FROM AGENT PLATFORM ({service_type}) ---")
        print(f"Status Code: {response_obj.status_code}")
        print(f"Response Text (first 1000 chars): {response_obj.text[:1000]}")  # Print first 1000 chars
        if len(response_obj.text) > 1000:
            print("... (response truncated)")
        print(f"--- [DEBUG] END OF RAW RESPONSE ({service_type}) ---")

        response_obj.raise_for_status()

        try:
            result_json = response_obj.json()
        except requests.exceptions.JSONDecodeError as jde:
            print(f"--- [DEBUG] ERROR PARSING AGENT PLATFORM JSON ({service_type}) ---")
            print(f"Error: {jde}")
            print(f"--- [DEBUG] END API CALL ({service_type}) ---")
            return None, f"Agent平台响应非JSON ({service_type}): {str(jde)}. Raw Resp: {response_obj.text[:500]}"

        if not isinstance(result_json, dict):
            print(f"--- [DEBUG] AGENT PLATFORM RESPONSE NOT A DICT ({service_type}) ---")
            print(f"Type: {type(result_json)}")
            print(f"--- [DEBUG] END API CALL ({service_type}) ---")
            return None, f"Agent平台响应顶层非JSON对象 ({service_type}). 类型: {type(result_json)}. Raw Resp: {response_obj.text[:500]}"

        if result_json.get("status") != 0:
            print(f"--- [DEBUG] API BUSINESS ERROR ({service_type}) ---")
            print(f"Full API Result: {result_json}")
            print(f"--- [DEBUG] END API CALL ({service_type}) ---")
            return None, f"API业务错误 ({service_type}): {result_json.get('message', '未知API业务错误')}. Full Resp: {result_json}"

        llm_response_str = ""
        data_field = result_json.get("data")

        if data_field is None:
            print(f"--- [DEBUG] 'data' FIELD IS None IN API RESPONSE ({service_type}) ---")
            print(f"Full API Result: {result_json}")
            # 根据您最新的错误信息，这里会是导致问题的地方
            # 为了调试，我们先不立即返回错误，而是继续让之前的逻辑触发错误，以确认
            # 或者，如果您想让它直接因为这个None而“失败”，可以取消下面这行的注释
            # return None, f"调试：'data'字段为None. API Result: {result_json}"
        elif not isinstance(data_field, dict):
            print(f"--- [DEBUG] 'data' FIELD IS NOT A DICT ({service_type}) ---")
            print(f"Type of data_field: {type(data_field)}")
            print(f"Full API Result: {result_json}")
            print(f"--- [DEBUG] END API CALL ({service_type}) ---")
            return None, f"LLM响应中'data'字段类型错误 (应为dict, 得到 {type(data_field)}) ({service_type}). API Result: {result_json}"

        # 只有当 data_field 是字典时才尝试获取 content
        if isinstance(data_field, dict):
            content_list = data_field.get("content", [])
            if content_list and isinstance(content_list, list) and len(content_list) > 0:
                first_content_item = content_list[0]
                if isinstance(first_content_item, dict):
                    raw_data_str = first_content_item.get("data", "")
                    if raw_data_str.startswith("```json\n"): raw_data_str = raw_data_str[len("```json\n"):]
                    if raw_data_str.endswith("\n```"): raw_data_str = raw_data_str[:-len("\n```")]
                    llm_response_str = raw_data_str.strip()
                    print(f"--- [DEBUG] EXTRACTED LLM RESPONSE STRING ({service_type}) ---")
                    print(llm_response_str)
                    print(f"--- [DEBUG] END OF LLM RESPONSE STRING ({service_type}) ---")
                else:
                    print(f"--- [DEBUG] 'data.content[0]' IS NOT A DICT ({service_type}) ---")
                    print(f"Full API Result: {result_json}")
                    print(f"--- [DEBUG] END API CALL ({service_type}) ---")
                    return None, f"LLM响应中'data.content[0]'不是有效对象 ({service_type}). API Result: {result_json}"
            else:
                print(f"--- [DEBUG] 'data.content' IS EMPTY OR INVALID ({service_type}) ---")
                print(f"Full API Result: {result_json}")
                # 如果是讨论服务且希望空content被视为空对话，可以加逻辑，但现在仅为调试
                # 此处不直接返回错误，让后续的llm_response_str检查处理
        else:  # data_field is None or not a dict (前面已经有分支处理了，但为保险)
            print(
                f"--- [DEBUG] SKIPPING 'content' EXTRACTION BECAUSE 'data_field' IS NOT A DICT OR IS None ({service_type}) ---")

        if not llm_response_str:
            print(f"--- [DEBUG] LLM RESPONSE STRING IS EMPTY AFTER PARSING ({service_type}) ---")
            print(f"Full API Result (from Agent Platform): {result_json}")
            print(f"--- [DEBUG] END API CALL ({service_type}) ---")
            # 这里的return会触发你看到的“LLM响应为空或解析失败”或“'data'字段缺失或类型错误”等
            # 具体取决于之前的逻辑分支。
            # 为了与您上次的错误信息对应（'data'字段缺失或类型错误），这里的直接返回可能被之前的data_field is None检查覆盖
            # 我们让它自然地走到下一个错误点，或者您已经知道问题出在 data_field is None

            # 以下是为了复现您看到的错误 "LLM响应中'data'字段缺失或类型错误"
            # 我们需要让代码走到原始产生该错误的地方
            # 在这种情况下，如果 llm_response_str 为空是因为 data_field is None,
            # 那么错误应该在 `if not isinstance(data_field, dict):` 或 `if data_field is None:` 处被捕获
            # 这段 `if not llm_response_str:` 的主要作用是捕获那些 `data` 和 `content` 结构都正常，但最终解析出的 `data` 字符串为空的情况。

            # 暂时保持原逻辑，让错误在 data_field 检查处抛出
            pass  # 允许代码继续，以便之前的错误检查（如 data_field is None）能按原样触发

        # 尝试解析实际LLM的响应字符串（应为JSON）
        try:
            # 只有当llm_response_str非空时才尝试解析
            if llm_response_str:
                parsed_llm_output = json.loads(llm_response_str)
                print(f"--- [DEBUG] PARSED LLM OUTPUT (from llm_response_str) ({service_type}) ---")
                print(json.dumps(parsed_llm_output, ensure_ascii=False, indent=2))
                print(f"--- [DEBUG] END OF PARSED LLM OUTPUT ({service_type}) ---")
                print(f"--- [DEBUG] END API CALL ({service_type}) ---")
                return parsed_llm_output, None
            elif data_field is None and service_type == "discussion":  # 处理上一个错误信息的情况
                print(f"--- [DEBUG] RETURNING EMPTY DIALOGUE DUE TO 'data' BEING None ({service_type}) ---")
                print(f"--- [DEBUG] END API CALL ({service_type}) ---")
                # 如果您想直接解决 data:None 的问题，可以取消下面这行的注释
                # return {"dialogues": []}, None
                # 但为了纯粹的打印调试，我们让它按照之前的逻辑失败
                return None, f"LLM响应中'data'字段缺失或类型错误 ({service_type}). API Result: {result_json}"  # 这会复现您看到的错误
            else:  # llm_response_str 为空，且不是 data:None 的特殊情况
                print(f"--- [DEBUG] LLM RESPONSE STRING IS EMPTY, CANNOT PARSE ({service_type}) ---")
                print(f"--- [DEBUG] END API CALL ({service_type}) ---")
                return None, f"LLM ({service_type}) 响应字符串为空，无法解析. API Result: {result_json}"


        except json.JSONDecodeError as e_llm:
            print(f"--- [DEBUG] ERROR PARSING LLM RESPONSE STRING ({service_type}) ---")
            print(f"Error: {e_llm}")
            print(f"Raw LLM String that failed: '{llm_response_str}'")
            print(f"--- [DEBUG] END API CALL ({service_type}) ---")
            return None, f"LLM ({service_type}) 响应JSON解析错误: {e_llm}. Raw LLM Str: '{llm_response_str}'"

    except requests.exceptions.Timeout:
        print(f"--- [DEBUG] NETWORK TIMEOUT ({service_type}) ---")
        print(f"--- [DEBUG] END API CALL ({service_type}) ---")
        return None, f"网络超时错误 ({service_type})"
    except requests.exceptions.RequestException as e:
        print(f"--- [DEBUG] NETWORK OR REQUEST ERROR ({service_type}) ---")
        print(f"Error: {e}")
        print(f"--- [DEBUG] END API CALL ({service_type}) ---")
        return None, f"网络连接或请求错误 ({service_type}): {str(e)}"
    except Exception as e:
        print(f"--- [DEBUG] UNKNOWN ERROR IN API CALL PROCESSING ({service_type}) ---")
        print(f"Error Type: {type(e)}")
        print(f"Error: {e}")
        import traceback
        traceback.print_exc()  # 打印完整的堆栈跟踪
        error_context = ""
        if response_obj is not None:
            try:
                error_context = f" Raw Resp (if available): {response_obj.text[:500]}"
            except:
                error_context = " Raw Resp not available or failed to retrieve."
        print(f"--- [DEBUG] END API CALL ({service_type}) ---")
        return None, f"处理API响应时发生未知内部错误 ({service_type}): {str(e)}.{error_context}"


# --- MOCK API Functions (增加延迟) ---
def mock_call_discussion_api(character_identities, current_team, history, player_perspectives):
    print("MOCK: Calling Discussion API (simulating delay)...")
    time.sleep(2.5)  # 模拟网络延迟
    dialogues = []
    # (其余mock逻辑不变)
    for player in PLAYER_NAMES:
        role = character_identities[player]
        perspective = player_perspectives[player]
        trust_str = ", ".join(perspective['trust']) if perspective['trust'] else "无人"
        suspect_str = ", ".join(perspective['suspect']) if perspective['suspect'] else "无人"
        dialogue = f"{player} ({role}): "
        if player in current_team:
            dialogue += f"我在队伍 {current_team} 中。 "
            if role == "梅林":
                dialogue += "我能看到邪恶，这次队伍里应该安全。"
            elif ROLE_FACTIONS[role] == "Red" and any(
                ROLE_FACTIONS[character_identities[p]] == "Blue" for p in current_team):
                dialogue += "哼，我会尽力破坏任务的！"
            else:
                dialogue += "我觉得这个队伍可以。"
        else:
            dialogue += f"队伍 {current_team} 没有我。 "
            if ROLE_FACTIONS[role] == "Blue":
                dialogue += "希望他们能成功。"
            else:
                dialogue += "希望他们失败，嘿嘿。"
        dialogue += f" 我信任 {trust_str}，怀疑 {suspect_str}。"
        dialogues.append(dialogue)
    return {"dialogues": dialogues}, None


def mock_call_analysis_api(dialogues, game_state):
    print("MOCK: Calling Analysis API (simulating delay)...")
    time.sleep(2)  # 模拟网络延迟
    new_perspectives = json.loads(json.dumps(game_state["player_perspectives"]))  # Deep copy
    # (其余mock逻辑不变)
    last_mission = game_state["history"][-1] if game_state["history"] else None
    if last_mission:
        team_involved = last_mission["team"]
        mission_failed = last_mission["result"] == "失败"
        for player_name, perspective_data in new_perspectives.items():
            current_trust = set(perspective_data["trust"])
            current_suspect = set(perspective_data["suspect"])
            if mission_failed:
                for member in team_involved:
                    if member != player_name and ROLE_FACTIONS[game_state["character_identities"][member]] != \
                            ROLE_FACTIONS[game_state["character_identities"][player_name]]:
                        current_suspect.add(member)
                        if member in current_trust: current_trust.remove(member)
            else:  # Mission succeeded
                for member in team_involved:
                    if member != player_name and ROLE_FACTIONS[game_state["character_identities"][member]] == \
                            ROLE_FACTIONS[game_state["character_identities"][player_name]]:
                        current_trust.add(member)
                        if member in current_suspect: current_suspect.remove(member)
            new_perspectives[player_name]["trust"] = sorted(list(current_trust))
            new_perspectives[player_name]["suspect"] = sorted(list(current_suspect))
    return {"player_perspectives": new_perspectives}, None


# --- Game Logic Class (`AvalonGame` - 保持不变) ---
class AvalonGame:
    def __init__(self):
        self.players = PLAYER_NAMES[:]
        random.shuffle(self.players)
        self.roles = ROLES_FULL_SET[:] # 使用更新后的角色列表
        random.shuffle(self.roles)
        self.character_identities = {}
        self.player_perspectives = {}
        self.current_round = 0
        self.mission_history = []
        self.blue_wins = 0
        self.red_wins = 0
        self.current_leader_index = -1  # 初始化为-1，第一轮start_new_round会变成0
        self.current_team_proposal = []
        self.last_mission_dialogues = []
        self._assign_roles_and_initial_perspectives()

    def _assign_roles_and_initial_perspectives(self):

        assigned_roles = self.roles[:]
        for player in self.players:
            self.character_identities[player] = assigned_roles.pop()
        for player_name in self.players:
            self.player_perspectives[player_name] = {"trust": [], "suspect": []}
        merlin = percival = morgana = assassin = None
        evil_team = []
        for p, r in self.character_identities.items():
            if r == "梅林": merlin = p
            if r == "派西维尔": percival = p
            if r == "莫甘娜": morgana = p
            #if r == "莫德雷德": mordred = p
            if r == "刺客": assassin = p
            if ROLE_FACTIONS[r] == "Red": evil_team.append(p)
        if merlin:
            for p, r in self.character_identities.items():
                # MODIFIED: Merlin sees all Red players (since Mordred isn't a special case for not being seen)
                if ROLE_FACTIONS[r] == "Red": # 莫德雷德已移除，梅林能看到所有红方
                    self.player_perspectives[merlin]["suspect"].append(p)
                elif ROLE_FACTIONS[r] == "Blue" and p != merlin:
                    self.player_perspectives[merlin]["trust"].append(p)
        if percival:
            if merlin: self.player_perspectives[percival]["suspect"].append(merlin)
            if morgana: self.player_perspectives[percival]["suspect"].append(morgana)
        # All evil team members know each other
        for evil_player in evil_team:
            # 由于莫德雷德已移除，此处的逻辑自然适用：所有坏人互相认识
            for other_evil in evil_team:
                if other_evil != evil_player:
                    self.player_perspectives[evil_player]["trust"].append(other_evil)
            for p, r in self.character_identities.items():
                if ROLE_FACTIONS[r] == "Blue":
                    self.player_perspectives[evil_player]["suspect"].append(p)

        for p in self.players: # Sort and unique
            self.player_perspectives[p]["trust"] = sorted(list(set(self.player_perspectives[p]["trust"])))
            self.player_perspectives[p]["suspect"] = sorted(list(set(self.player_perspectives[p]["suspect"])))

    def get_game_state_for_api(self):
        # (代码保持不变)
        return {
            "character_identities": self.character_identities,  # 通常不直接给讨论模型，但分析模型可能需要
            "current_team": self.current_team_proposal,
            "history": self.mission_history,
            "player_perspectives": self.player_perspectives,
            "current_round": self.current_round,
            "score": {"blue": self.blue_wins, "red": self.red_wins},
            "team_size_for_current_round": ROUND_TEAM_SIZES.get(self.current_round, "N/A"),
            "leader": self.players[self.current_leader_index] if self.current_leader_index >= 0 else "N/A"
        }

    def start_new_round(self):
        # (代码保持不变)
        self.current_round += 1
        if self.current_round > 5: return False
        self.current_leader_index = (self.current_leader_index + 1) % len(self.players)
        team_size = ROUND_TEAM_SIZES[self.current_round]
        # 简单地从当前队长开始顺时针选择足够的玩家作为初始提议
        start_idx = self.current_leader_index
        self.current_team_proposal = [self.players[(start_idx + i) % len(self.players)] for i in range(team_size)]
        # self.current_team_proposal = random.sample(self.players, team_size) # 或者保持随机
        return True

    def call_discussion_llm(self):
        # (代码保持不变，payload构造也保持不变)
        payload_for_discussion = {
            "game_info": {
                "current_round": self.current_round,
                "team_size_for_current_round": ROUND_TEAM_SIZES.get(self.current_round, "N/A"),
                "score": {"blue_wins": self.blue_wins, "red_wins": self.red_wins},
                "leader": self.players[self.current_leader_index],
                "proposed_team": self.current_team_proposal,
                 # MODIFIED: Add note about round 4 special rule if applicable for LLM context
                "special_rules_note": "本局为第4轮，且玩家数>=7，任务失败需要至少2张失败票。" if len(self.players) >= 7 and self.current_round == 4 else ""

            },
            "player_identities_and_perspectives": {
                player: {
                    "role": self.character_identities[player],
                    "faction": ROLE_FACTIONS[self.character_identities[player]],
                    "perspective": self.player_perspectives[player]
                } for player in self.players
            },
            "mission_history": self.mission_history
        }
        # print("\n[DEBUG] 调用讨论模拟API - 输入:", json.dumps(payload_for_discussion, indent=2, ensure_ascii=False))
        if use_real_api:
            response_data, error = call_wenxin_api_actual(payload_for_discussion, service_type="discussion")
        else:
            response_data, error = mock_call_discussion_api(
                self.character_identities, self.current_team_proposal, self.mission_history, self.player_perspectives
            )
        # print("[DEBUG] 讨论模拟API响应 - 输出:", json.dumps(response_data, indent=2, ensure_ascii=False) if response_data else f"错误: {error}")
        if error:
            self.last_mission_dialogues = []
            return None, error
        self.last_mission_dialogues = response_data.get("dialogues", [])
        return self.last_mission_dialogues, None

    def determine_mission_result(self, team_on_mission):
        num_evil_on_team = sum(
            1 for p_name in team_on_mission if ROLE_FACTIONS[self.character_identities[p_name]] == "Red")

        # MODIFIED: Implement Round 4 special rule for 7+ player games (covers 8-player)
        # For 7+ player games, round 4 mission requires 2 fails to fail.
        if len(self.players) >= 7 and self.current_round == 4:
            # self.log_message(f"  (第四轮特殊规则检定：队伍中红方数量 {num_evil_on_team})", no_newline=True) # GUI log  <--- 此行已移除或注释掉
            return "失败" if num_evil_on_team >= 2 else "成功"
        else: # Standard rule for other rounds/player counts
            return "失败" if num_evil_on_team >= 1 else "成功"

    def record_mission_result(self, result):
        # (代码保持不变)
        self.mission_history.append({
            "round": self.current_round,
            "team": self.current_team_proposal[:],
            "result": result,
            "leader": self.players[self.current_leader_index]
        })
        if result == "成功":
            self.blue_wins += 1
        else:
            self.red_wins += 1

    def call_analysis_llm(self):
        # (代码保持不变，payload构造也保持不变)
        payload_for_analysis = {
            "game_state": self.get_game_state_for_api(),
            "dialogues_from_last_round": self.last_mission_dialogues,
            "last_mission_result": self.mission_history[-1] if self.mission_history else None

        }
        # print("\n[DEBUG] 调用任务结果分析API - 输入:", json.dumps(payload_for_analysis, indent=2, ensure_ascii=False))
        if use_real_api:
            response_data, error = call_wenxin_api_actual(payload_for_analysis, service_type="analysis")
        else:
            response_data, error = mock_call_analysis_api(
                self.last_mission_dialogues, self.get_game_state_for_api()
            )
        # print("[DEBUG] 任务结果分析API响应 - 输出:", json.dumps(response_data, indent=2, ensure_ascii=False) if response_data else f"错误: {error}")
        if error: return False, error
        updated_perspectives = response_data.get("player_perspectives")
        if updated_perspectives and isinstance(updated_perspectives, dict):
            # (数据结构验证逻辑保持不变)
            valid_update = True
            for player, persp_data in updated_perspectives.items():
                if not (player in self.players and isinstance(persp_data, dict) and \
                        "trust" in persp_data and isinstance(persp_data["trust"], list) and \
                        "suspect" in persp_data and isinstance(persp_data["suspect"], list) and \
                        all(isinstance(name, str) for name in persp_data["trust"]) and \
                        all(isinstance(name, str) for name in persp_data["suspect"])):
                    valid_update = False;
                    print(f"玩家 {player} 的视角数据格式无效: {persp_data}");
                    break
            if valid_update:
                self.player_perspectives = updated_perspectives
                for p_name in self.player_perspectives:  # 确保去重和排序
                    self.player_perspectives[p_name]["trust"] = sorted(
                        list(set(p for p in self.player_perspectives[p_name]["trust"] if p in self.players)))
                    self.player_perspectives[p_name]["suspect"] = sorted(list(set(
                        p for p in self.player_perspectives[p_name]["suspect"] if p in self.players and p != p_name)))
                return True, None
            else:
                return False, "分析API返回的视角数据结构不正确"
        return False, "分析API未返回有效的视角数据或格式错误"

    def check_game_over(self):
        # (代码保持不变)
        if self.blue_wins >= 3: return "ASSASSIN_PHASE", "蓝色阵营任务胜利! 刺客准备行动..."
        if self.red_wins >= 3: return "红色阵营", "红色阵营任务胜利 (3次任务失败)!"
        if self.current_round >= 5:
            if self.blue_wins < 3: return "红色阵营", "红色阵营胜利 (5轮任务结束, 蓝色方未能完成3次任务)!"
        return None, None

    def get_assassin_and_merlin(self):  # (代码保持不变)
        return next((p for p, r in self.character_identities.items() if r == "刺客"), None), \
            next((p for p, r in self.character_identities.items() if r == "梅林"), None)

    def assassin_attempt(self, target_player_name):  # (代码保持不变)
        assassin, merlin = self.get_assassin_and_merlin()
        if not assassin or not merlin: return "蓝色阵营", "错误: 找不到刺客或梅林角色!"
        if target_player_name == merlin:
            return "红色阵营", f"刺杀成功! {target_player_name} ({self.character_identities[merlin]}) 被刺杀. 红色阵营反败为胜!"
        return "蓝色阵营", f"刺杀失败! {target_player_name} ({self.character_identities.get(target_player_name, '未知')}) 不是梅林. 蓝色阵营最终胜利!"


# --- Tkinter GUI Class (`AvalonGUI`) ---
class AvalonGUI(tk.Tk):
    def __init__(self, game):
        super().__init__()
        self.game = game
        self.title("类阿瓦隆桌游模拟器")
        self.geometry("1000x750")

        # 字体定义 (保持不变)
        self.default_font_family = "Microsoft YaHei UI"
        try:
            tkfont.Font(family=self.default_font_family, size=12).actual()
        except tk.TclError:
            self.default_font_family = "SimSun"
        try:
            tkfont.Font(family=self.default_font_family, size=12).actual()
        except tk.TclError:
            self.default_font_family = tkfont.nametofont("TkDefaultFont").actual()["family"]
        self.default_font = (self.default_font_family, 12)
        self.bold_font = (self.default_font_family, 12, "bold")
        self.title_font = (self.default_font_family, 14, "bold")
        self.text_area_font = (self.default_font_family, 11)
        self.loading_font = (self.default_font_family, 11, "italic")  # 加载提示字体

        self.api_call_active = False  # 标志API调用是否正在进行
        self.result_queue = queue.Queue()  # 用于线程通信
        self.user_overriding_team_proposal = False  # 新增：标志用户是否正在覆盖提议

        self._apply_styles()
        self._init_widgets()
        self.protocol("WM_DELETE_WINDOW", self._on_closing)  # 处理关闭事件
        self.start_game()

    def _on_closing(self):
        if self.api_call_active:
            if messagebox.askokcancel("退出", "API调用仍在进行中，确定要退出吗？"):
                # 这里可以尝试设置一个标志来中断线程中的循环（如果适用）
                # 但对于requests库，中断正在进行的HTTP请求比较困难
                self.destroy()
        else:
            self.destroy()

    def _apply_styles(self):  # (保持不变)
        style = ttk.Style(self)
        available_themes = style.theme_names()
        if 'clam' in available_themes:
            style.theme_use('clam')
        elif 'vista' in available_themes:
            style.theme_use('vista')
        elif 'aqua' in available_themes:
            style.theme_use('aqua')
        style.configure("TButton", font=self.default_font, padding=5)
        style.configure("TLabel", font=self.default_font, padding=5)
        style.configure("TCheckbutton", font=self.default_font, padding=(5, 2))
        style.configure("TLabelframe", font=self.default_font, padding=10)
        style.configure("TLabelframe.Label", font=self.title_font, padding=(5, 2))
        # 为加载中的按钮添加一个特殊样式
        style.configure("Loading.TButton", font=self.default_font, padding=5)  # 可以基于TButton，或者自定义颜色

    def _init_widgets(self):  # (大部分保持不变，添加loading_label)
        main_frame = ttk.Frame(self, padding="10 10 10 10")
        main_frame.pack(fill=tk.BOTH, expand=True)

        self.status_frame = ttk.LabelFrame(main_frame, text="游戏状态")
        self.status_frame.pack(pady=10, padx=5, fill="x")
        self.team_selection_frame = ttk.LabelFrame(main_frame, text="队伍选择")
        self.team_selection_frame.pack(pady=10, padx=5, fill="x")
        self.discussion_frame = ttk.LabelFrame(main_frame, text="讨论区")
        self.discussion_frame.pack(pady=10, padx=5, fill="both", expand=True)
        self.action_frame = ttk.Frame(main_frame)
        self.action_frame.pack(pady=10, padx=5, fill="x")

        self.round_label = ttk.Label(self.status_frame, text="回合: -")
        self.round_label.pack(side=tk.LEFT, padx=10)
        self.score_label = ttk.Label(self.status_frame, text="计分板: 蓝 0 - 0 红")
        self.score_label.pack(side=tk.LEFT, padx=10)
        self.current_team_label = ttk.Label(self.status_frame, text="当前队伍提议: []")
        self.current_team_label.pack(side=tk.LEFT, padx=10, expand=True, fill=tk.X)
        self.roles_button = ttk.Button(self.status_frame, text="查看身份(调试用)", command=self._show_roles_debug)
        self.roles_button.pack(side=tk.RIGHT, padx=5)

        self.player_checkbuttons = {}
        self.player_vars = {}

        self.discussion_text = tk.Text(self.discussion_frame, wrap=tk.WORD, height=15, state=tk.DISABLED,
                                       font=self.text_area_font, relief=tk.SOLID, borderwidth=1)
        self.discussion_text_scrollbar = ttk.Scrollbar(self.discussion_frame, command=self.discussion_text.yview)
        self.discussion_text.config(yscrollcommand=self.discussion_text_scrollbar.set)
        self.discussion_text_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.discussion_text.pack(side=tk.LEFT, fill="both", expand=True, padx=(0, 5))

        # 加载提示标签，初始隐藏
        self.loading_label = ttk.Label(self.discussion_frame, text="", font=self.loading_font, foreground="blue")
        # self.loading_label.place(relx=0.5, rely=0.5, anchor=tk.CENTER) # 另一种放置方式

        self.agree_button = ttk.Button(self.action_frame, text="同意组队并执行任务", command=self.on_agree_team,
                                       state=tk.DISABLED)
        self.agree_button.pack(side=tk.LEFT, padx=5, ipadx=5, ipady=2)
        self.change_team_button = ttk.Button(self.action_frame, text="更换组队角色",
                                             command=self.on_change_team_proposal, state=tk.DISABLED)
        self.change_team_button.pack(side=tk.LEFT, padx=5, ipadx=5, ipady=2)
        self.propose_new_team_button = ttk.Button(self.action_frame, text="确定新队伍人选",
                                                  command=self.on_propose_new_team, state=tk.DISABLED)
        # propose_new_team_button 不立即pack

    def _show_roles_debug(self):  # (保持不变)
        if self.api_call_active: return
        roles_info = "角色分配:\n"  # ... (其余代码不变)
        for player, role in self.game.character_identities.items():
            roles_info += f"{player}: {role} ({ROLE_FACTIONS[role]})\n"
        roles_info += "\n初始视角:\n"
        for player, perspectives in self.game.player_perspectives.items():
            trust = ", ".join(perspectives['trust']) or "无"
            suspect = ", ".join(perspectives['suspect']) or "无"
            roles_info += f"{player} (信任: {trust} | 怀疑: {suspect})\n"
        info_win = Toplevel(self)
        info_win.title("角色信息 (调试)")
        info_win.geometry("400x500")
        info_text = tk.Text(info_win, wrap=tk.WORD, font=self.text_area_font, padx=10, pady=10)
        info_text.insert(tk.END, roles_info)
        info_text.config(state=tk.DISABLED)
        info_text.pack(expand=True, fill=tk.BOTH)
        ttk.Button(info_win, text="关闭", command=info_win.destroy).pack(pady=5)
        info_win.transient(self);
        info_win.grab_set()

    def _set_ui_for_api_call(self, active, message=""):
        """统一处理API调用前后的UI状态"""
        self.api_call_active = active
        if active:
            self.config(cursor="watch")  # 更改鼠标光标
            self.agree_button.config(state=tk.DISABLED)
            self.change_team_button.config(state=tk.DISABLED)
            self.propose_new_team_button.config(state=tk.DISABLED)
            self.roles_button.config(state=tk.DISABLED)  # 调试按钮也禁用
            self.loading_label.config(text=message)
            self.loading_label.pack(pady=5)  # 显示加载提示
        else:
            self.config(cursor="")
            # 按钮状态恢复由各自的逻辑处理，这里只恢复通用部分
            self.roles_button.config(state=tk.NORMAL)
            self.loading_label.pack_forget()  # 隐藏加载提示
            self.loading_label.config(text="")

    def _threaded_api_call(self, api_function, *args):
        """在新线程中执行API调用"""

        def wrapper():
            try:
                result, error = api_function(*args)
                self.result_queue.put((result, error))
            except Exception as e:  # 捕获线程内所有异常
                self.result_queue.put((None, f"线程内API调用异常: {e}"))

        thread = threading.Thread(target=wrapper)
        thread.daemon = True  # 确保主程序退出时线程也退出
        thread.start()
        return thread

    def _check_api_result(self, callback_success, callback_failure=None):
        """定期检查API结果队列，并在获取结果后执行回调"""
        try:
            result, error = self.result_queue.get_nowait()  # 非阻塞获取
            self._set_ui_for_api_call(False)  # 恢复UI

            if error:
                self.log_message(f"API调用错误: {error}")
                messagebox.showerror("API错误", f"API调用失败: {error}", parent=self)
                if callback_failure:
                    callback_failure()  # 执行失败回调
                else:  # 默认的失败处理，如果需要，可以恢复某些按钮
                    self.agree_button.config(state=tk.NORMAL)  # 示例：允许重试
                    self.change_team_button.config(state=tk.NORMAL)
            else:
                callback_success(result)  # 执行成功回调

        except queue.Empty:  # 如果队列为空，则表示API仍在运行
            self.after(100, lambda: self._check_api_result(callback_success, callback_failure))  # 100ms后再次检查
        except Exception as e:  # 捕获检查过程中的其他异常
            self._set_ui_for_api_call(False)
            self.log_message(f"处理API结果时发生内部错误: {e}")
            messagebox.showerror("内部错误", f"处理API结果时发生错误: {e}", parent=self)
            if callback_failure: callback_failure()

    def start_game(self):
        self.log_message("游戏开始！正在初始化...")
        if self.game.start_new_round():
            self.update_ui_for_new_proposal()
            self.initiate_discussion()
        else:
            self.log_message("错误：无法开始第一轮游戏。")
            messagebox.showerror("错误", "无法开始游戏，请检查游戏逻辑。", parent=self)
            self.end_game_ui_state()

    def update_ui_for_new_proposal(self):
        self.round_label.config(text=f"回合: {self.game.current_round}")
        self.score_label.config(text=f"计分板: 蓝 {self.game.blue_wins} - {self.game.red_wins} 红")

        proposal_text = ""
        if self.user_overriding_team_proposal:  # 检查标志
            proposal_text = f"场外提议队伍: {', '.join(self.game.current_team_proposal)}"
        else:
            leader_name = self.game.players[self.game.current_leader_index]
            proposal_text = f"队长 {leader_name} 提议: {', '.join(self.game.current_team_proposal)}"
        self.current_team_label.config(text=proposal_text)

        self.agree_button.config(state=tk.NORMAL)
        self.change_team_button.config(state=tk.NORMAL)
        self.propose_new_team_button.pack_forget()
        self._clear_team_selection_widgets()
        self.team_selection_frame.config(text="队伍选择")  # 恢复默认标题，因为选择过程已结束或将开始

    def _clear_team_selection_widgets(self):  # (保持不变)
        for widget in self.team_selection_frame.winfo_children(): widget.destroy()
        self.player_checkbuttons = {};
        self.player_vars = {}

    def initiate_discussion(self):
        if self.api_call_active: return
        self._set_ui_for_api_call(True, "正在生成讨论内容 (API调用中)...")

        log_message_prefix = ""
        if self.user_overriding_team_proposal:  # 检查标志
            log_message_prefix = f"\n--- 第 {self.game.current_round} 轮：场外提议队伍 {self.game.current_team_proposal} ---"
        else:
            leader_name = self.game.players[self.game.current_leader_index]
            log_message_prefix = f"\n--- 第 {self.game.current_round} 轮：队长 {leader_name} 提议队伍 {self.game.current_team_proposal} ---"
        self.log_message(log_message_prefix)

        # MODIFIED: Add note to log for round 4 in UI
        if len(self.game.players) >= 7 and self.game.current_round == 4:
            self.log_message(f"(注意: 本轮为第4轮，玩家数 {len(self.game.players)} >= 7, 任务失败需要至少2张失败票)")
        self.log_message("调用讨论模拟API...")

        self._threaded_api_call(self.game.call_discussion_llm)
        self._check_api_result(self._handle_discussion_result, self._handle_discussion_failure)

        # 重置标志位，因为发起讨论后，这个覆盖状态就结束了
        self.user_overriding_team_proposal = False

    def _handle_discussion_result(self, dialogues):
        """讨论API成功后的回调"""
        if dialogues:
            self.log_message("讨论内容:")
            self._display_dialogue_incrementally(dialogues)  # 这个函数内部会恢复按钮状态
        else:
            self.log_message("无讨论内容返回。")
            self.agree_button.config(state=tk.NORMAL)
            self.change_team_button.config(state=tk.NORMAL)

    def _handle_discussion_failure(self):
        """讨论API失败后的回调"""
        # API调用失败时，也允许玩家继续操作或重试
        self.log_message("讨论生成失败，请检查错误信息。")
        self.agree_button.config(state=tk.NORMAL)
        self.change_team_button.config(state=tk.NORMAL)

    def _display_dialogue_incrementally(self, dialogues, index=0):  # (保持不变，但按钮状态由调用者管理)
        if index < len(dialogues):
            self.log_message(dialogues[index])
            self.after(150, self._display_dialogue_incrementally, dialogues, index + 1)
        else:
            self.log_message("--- 讨论结束 ---")
            # 按钮状态恢复应在API调用完成时，而不是对话显示完成时
            # 所以从这里移除按钮状态的恢复，放到 _handle_discussion_result 和 _check_api_result 中
            if not self.api_call_active:  # 确保不是因为另一个API调用而被禁用
                self.agree_button.config(state=tk.NORMAL)
                self.change_team_button.config(state=tk.NORMAL)

    def log_message(self, message, no_newline=False):  # (保持不变)
        self.discussion_text.config(state=tk.NORMAL)
        self.discussion_text.insert(tk.END, message + (" " if no_newline else "\n"))
        self.discussion_text.see(tk.END)
        self.discussion_text.config(state=tk.DISABLED)
        self.update_idletasks()  # 确保消息立即显示

    def on_agree_team(self):
        if self.api_call_active: return
        self.agree_button.config(state=tk.DISABLED)
        self.change_team_button.config(state=tk.DISABLED)

        team_on_mission = self.game.current_team_proposal
        self.log_message(f"\n队伍 {team_on_mission} 被同意，开始执行任务...")

        # 将 AvalonGame 中的 log_message 移到这里，以便 GUI 控制日志输出
        # result = self.game.determine_mission_result(team_on_mission)
        # ^^^ 这行会调用 game.log_message，但我们希望 GUI 控制日志，所以在 game.determine_mission_result 中移除 log_message 调用

        # 调用 determine_mission_result 前，先获取红方数量用于日志
        num_evil_on_team_for_log = sum(
            1 for p_name in team_on_mission if ROLE_FACTIONS[self.game.character_identities[p_name]] == "Red"
        )

        # 在 AvalonGame.determine_mission_result 中移除 self.log_message 调用
        # 然后在这里进行日志记录
        original_determine_mission_result = self.game.determine_mission_result

        # 暂时修改 AvalonGame 实例的 log_message 方法为空操作，避免重复记录
        # 或更好的是，直接在 AvalonGame.determine_mission_result 中不调用 log_message
        # 假设 AvalonGame.determine_mission_result 不再调用 self.log_message
        result = self.game.determine_mission_result(team_on_mission)

        # MODIFIED: Log specific reason for round 4 if applicable
        if len(self.game.players) >= 7 and self.game.current_round == 4:
            if result == "失败":
                self.log_message(f"任务结果: {result} (第4轮保护轮，队伍中红方人数: {num_evil_on_team_for_log} >= 2)")
            else:
                self.log_message(f"任务结果: {result} (第4轮保护轮，队伍中红方人数: {num_evil_on_team_for_log} < 2)")
        else:
            self.log_message(f"任务结果: {result}")

        self.game.record_mission_result(result)
        self.score_label.config(text=f"计分板: 蓝 {self.game.blue_wins} - {self.game.red_wins} 红")

        game_status, reason = self.game.check_game_over()
        if game_status:
            if game_status == "ASSASSIN_PHASE":
                self.log_message(reason)
                self.enter_assassin_phase()
            else:
                self.log_message(f"游戏结束! {reason}")
                messagebox.showinfo("游戏结束", reason, parent=self)
                self.end_game_ui_state()
            return

        self._set_ui_for_api_call(True, "正在分析任务结果 (API调用中)...")
        self.log_message("调用任务结果分析API...")
        self._threaded_api_call(self.game.call_analysis_llm)
        self._check_api_result(self._handle_analysis_result, self._handle_analysis_failure)

    def _handle_analysis_result(self, success_flag):
        """分析API成功后的回调"""
        if success_flag:
            self.log_message("玩家信任/怀疑度已更新。")
        else:  # success_flag is False, but no error string from call_analysis_llm means invalid data
            self.log_message("分析API未能成功更新玩家视角，或返回数据无效。")
            # messagebox.showwarning("分析API", "分析API未能成功更新玩家视角，或返回数据无效。", parent=self)

        self._proceed_to_next_round_or_end_game()

    def _handle_analysis_failure(self):
        """分析API失败后的回调"""
        self.log_message("任务结果分析失败，游戏继续...")
        self._proceed_to_next_round_or_end_game()  # 即使分析失败，也继续游戏

    def _proceed_to_next_round_or_end_game(self):
        """在分析API调用（无论成功与否）后，决定是否开始新回合或结束游戏"""
        if self.game.start_new_round():
            self.update_ui_for_new_proposal()
            self.initiate_discussion()  # 这会再次触发API调用流程
        else:  # 游戏因回合数满而结束
            final_status, final_reason = self.game.check_game_over()
            if final_status and final_status != "ASSASSIN_PHASE":
                self.log_message(f"游戏结束! {final_reason}")
                messagebox.showinfo("游戏结束", final_reason, parent=self)
                self.end_game_ui_state()
            elif not final_status:  # 如果是正常5轮结束但蓝方没赢，且没触发刺杀
                self.log_message("游戏结束! (无法开始新回合)")
                messagebox.showinfo("游戏结束", "所有回合已完成或无法继续。", parent=self)
                self.end_game_ui_state()
            # 如果是ASSASSIN_PHASE，则之前已经处理了

    def on_change_team_proposal(self):
        if self.api_call_active: return
        self.user_overriding_team_proposal = True  # 设置标志位

        self.agree_button.config(state=tk.DISABLED)
        self.change_team_button.config(state=tk.DISABLED)
        self.propose_new_team_button.pack(side=tk.LEFT, padx=5, ipadx=5, ipady=2)
        self.propose_new_team_button.config(state=tk.NORMAL)
        self._clear_team_selection_widgets()
        team_size_needed = ROUND_TEAM_SIZES.get(self.game.current_round, len(self.game.players))
        # leader_name = self.game.players[self.game.current_leader_index] # 不再需要获取和显示队长名
        self.team_selection_frame.config(text=f"重新选择队伍 (需 {team_size_needed} 人)")  # 修改标题
        self.log_message(f"\n场外指导：请重新选择队伍成员 (需要 {team_size_needed} 人):")  # 修改日志

        # MODIFIED: Add note for round 4 if applicable in UI
        if len(self.game.players) >= 7 and self.game.current_round == 4:
            self.log_message(f"(提示: 第4轮任务若失败需2张失败票，请谨慎选择)")

        for player_name in self.game.players:
            var = tk.IntVar()
            cb = ttk.Checkbutton(self.team_selection_frame, text=player_name, variable=var)
            cb.pack(anchor=tk.W, padx=10, pady=2)
            self.player_vars[player_name] = var
            if player_name in self.game.current_team_proposal: var.set(1)

    def on_propose_new_team(self):  # (保持不变)
        if self.api_call_active: return
        selected_players = [p for p, var in self.player_vars.items() if var.get() == 1]
        team_size_needed = ROUND_TEAM_SIZES.get(self.game.current_round, len(self.game.players))
        if len(selected_players) != team_size_needed:
            messagebox.showwarning("选择错误",
                                   f"你需要选择 {team_size_needed} 名玩家。当前选择了 {len(selected_players)} 名。",
                                   parent=self)
            return
        self.game.current_team_proposal = selected_players
        self.propose_new_team_button.config(state=tk.DISABLED);
        self.propose_new_team_button.pack_forget()
        self._clear_team_selection_widgets()
        self.team_selection_frame.config(text="队伍选择")
        self.update_ui_for_new_proposal()  # 更新UI，但不立即开始讨论
        self.initiate_discussion()  # 开始新一轮讨论，这将处理API调用

    def enter_assassin_phase(self):  # (基本保持不变，确保API调用不在进行)
        if self.api_call_active:
            self.log_message("API调用进行中，请稍后进入刺杀阶段。")
            # 可以考虑延迟执行此函数
            self.after(500, self.enter_assassin_phase)
            return

        self._set_ui_for_api_call(False)  # 确保UI是可交互的，如果之前有API调用卡住
        self.agree_button.config(state=tk.DISABLED)
        self.change_team_button.config(state=tk.DISABLED)
        # (其余刺杀阶段逻辑不变)
        self.propose_new_team_button.pack_forget()
        self._clear_team_selection_widgets()
        assassin, merlin = self.game.get_assassin_and_merlin()
        if not assassin or not merlin:
            messagebox.showerror("错误", "找不到刺客或梅林。", parent=self)
            self.end_game_ui_state();
            return
        self.log_message(f"{assassin} (刺客), 请选择你要刺杀的玩家 (你认为是梅林):")
        target_options = [p for p in self.game.players if
                          p != assassin and ROLE_FACTIONS[self.game.character_identities[p]] == "Blue"]
        if not target_options:
            final_reason = "刺客无可选好人目标（除自己外），蓝色阵营胜利。"
            self.log_message(f"游戏结束! {final_reason}")
            messagebox.showinfo("游戏结束", final_reason, parent=self)
            self.end_game_ui_state();
            return

        assassin_choice_window = Toplevel(self)
        assassin_choice_window.title(f"{assassin} (刺客) 选择目标");
        assassin_choice_window.geometry("350x450");
        assassin_choice_window.resizable(False, False)
        style = ttk.Style(assassin_choice_window);
        current_theme = ttk.Style(self).theme_use()
        if current_theme:
            try:
                style.theme_use(current_theme)
            except tk.TclError:
                pass
        style.configure("TLabel", font=self.default_font);
        style.configure("TButton", font=self.default_font)
        ttk.Label(assassin_choice_window, text=f"{assassin}, 选择你认为的梅林:", font=self.bold_font).pack(pady=10)
        listbox_frame = ttk.Frame(assassin_choice_window, padding=5);
        listbox_frame.pack(pady=5, padx=10, fill=tk.BOTH, expand=True)
        listbox = Listbox(listbox_frame, selectmode=tk.SINGLE, exportselection=False, font=self.default_font,
                          relief=tk.SOLID, borderwidth=1)
        for player_name in target_options: listbox.insert(tk.END, player_name)
        listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        listbox_scroll = ttk.Scrollbar(listbox_frame, orient=tk.VERTICAL, command=listbox.yview);
        listbox_scroll.pack(side=tk.RIGHT, fill=tk.Y)
        listbox.config(yscrollcommand=listbox_scroll.set)

        def confirm_assassination():
            selected_indices = listbox.curselection()
            if not selected_indices: messagebox.showwarning("未选择", "请选择一个目标。",
                                                            parent=assassin_choice_window); return
            target_player_name = listbox.get(selected_indices[0])
            assassin_choice_window.destroy()
            self.log_message(f"刺客 ({assassin}) 选择刺杀: {target_player_name}")
            _, final_reason = self.game.assassin_attempt(target_player_name)
            self.log_message(f"游戏结束! {final_reason}")
            messagebox.showinfo("游戏结束", final_reason, parent=self)
            self.end_game_ui_state()

        ttk.Button(assassin_choice_window, text="确认刺杀", command=confirm_assassination).pack(pady=10, ipadx=5,
                                                                                                ipady=2)
        assassin_choice_window.transient(self);
        assassin_choice_window.grab_set();
        self.wait_window(assassin_choice_window)

    def end_game_ui_state(self):
        self._set_ui_for_api_call(False)  # 确保API调用状态被重置
        self.agree_button.config(state=tk.DISABLED)
        self.change_team_button.config(state=tk.DISABLED)
        self.propose_new_team_button.pack_forget()
        self.propose_new_team_button.config(state=tk.DISABLED)
        self._clear_team_selection_widgets()
        self.log_message("\n--- 游戏已结束 ---")
        # self._show_roles_debug()


# --- Main ---
if __name__ == "__main__":
    use_real_api = True  # 改为 True 以尝试真实API调用

    if use_real_api:
        if any(val.startswith("YOUR_") or not val for val in [
            WENXIN_DISCUSSION_APP_ID, WENXIN_DISCUSSION_SECRET_KEY,
            WENXIN_ANALYSIS_APP_ID, WENXIN_ANALYSIS_SECRET_KEY
        ]):
            print("警告: 至少一个文心API凭证未正确设置。真实API调用可能失败。")
        print("配置为使用真实的文心API。")
    else:
        print("配置为使用MOCK API (模拟2-3秒延迟)。")

    game_instance = AvalonGame()
    # 确保 AvalonGame 的 log_message 方法在 GUI 中被正确使用或覆盖
    # 为了避免 AvalonGame.determine_mission_result 中的 log_message 与 GUI 的 log_message 冲突，
    # 最好确保 AvalonGame 类的方法不直接调用 self.log_message，或者 GUI 在调用前临时修改它。
    # 一个简单的处理方式是在 AvalonGame.determine_mission_result 中移除对 self.log_message 的调用。
    # 以下是如何在实例化后修改 determine_mission_result 中的 log_message 调用（如果存在）
    # 但更推荐直接修改 AvalonGame 类定义。
    # 假设我们已修改 AvalonGame.determine_mission_result 不再调用 self.log_message。

    # 绑定GUI的log_message到game实例，这样game内部如果需要打印信息，会通过GUI的log_message
    # 但这只对显式调用 self.log_message 有效。
    # game_instance.log_message = lambda message, no_newline=False: app.log_message(message, no_newline)
    # 上述绑定方式可能引入循环依赖或在app未完全初始化时出问题，
    # 最佳实践是让游戏逻辑类不直接处理UI日志。
    app = AvalonGUI(game_instance)
    app.mainloop()